package com.usvisatrack.core.common;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.resource.transaction.spi.TransactionStatus;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.usvisatrack.core.common.search.InternalSearch;
import com.usvisatrack.core.dao.model.User;

/**
 * 
 * @author YuCheng Hu
 *
 */
public class Factory {
	private static SessionFactory factory = null;
	private static Logger logger = LoggerFactory.getLogger(Factory.class);

	private static SessionFactory sessionFactory;
	// private static ServiceRegistry serviceRegistry;

	private static int indexBatchSize = 100;
	private static boolean noCommit = false; // blocks the commit function from
												// being called - this is useful
												// for unit testing.
	private static boolean autoRollback = false; // executes a rollback instead
													// of a commit when commit
													// is called - this is
													// useful for unit
													// testing.
	private static boolean noClose = false; // prevents the connection from
											// being closed. This is useful for
											// unit testing.

	/**
	 * Get Hibernate connection Session
	 * 
	 * @return
	 */
	public static Session getSession() {
		return Factory.getFactory().getCurrentSession();
	}

	/**
	 * Get session connection Get
	 * 
	 * @return
	 */
	public static SessionFactory getFactory() {
		if (Factory.factory == null)
			Factory.factory = Factory.initSession();

		return Factory.factory;
	}

	/**
	 * 
	 * @return
	 */
	public static SessionFactory initSession() {
		// Hibernate 4 init Session
		// return new Configuration().configure().buildSessionFactory();
		// Configuration configuration = new Configuration();
		// configuration.configure();
		// serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
		// sessionFactory = configuration.buildSessionFactory(serviceRegistry);

		// Hibernate 5 init Session
		// configures settings from hibernate.cfg.xml
		final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();

		try {
			sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
		} catch (Exception e) {
			logger.error("HIBERNATE DB SESSION INIT ERROR", e);
			// The registry would be destroyed by the SessionFactory, but we had trouble building the SessionFactory
			// so destroy it manually.
			StandardServiceRegistryBuilder.destroy(registry);
		}

		return sessionFactory;
	}

	/**
	 * 
	 */
	public static void beginTransaction() {
		// If transaction not exist, create one
		if (!(Factory.getSession().getTransaction() != null
				&& Factory.getSession().getTransaction().getStatus() == TransactionStatus.ACTIVE)) {
			Factory.getSession().getTransaction().begin();
		}
	}

	public static Transaction getTransaction() {
		return Factory.getSession().beginTransaction();
	}

	public static void commitTransaction() {
		if (isAutoRollback()) {
			Factory.rollbackTransaction();
			return;
		}
		if (Factory.getSession().getTransaction() != null
				&& Factory.getSession().getTransaction().getStatus() == TransactionStatus.ACTIVE) {
			if (!noCommit)
				Factory.getSession().getTransaction().commit();
			// zhaohc
			Factory.getSession().close();
		}
	}

	public static void commitTransaction(Transaction tx) {
		if (isAutoRollback()) {
			tx.rollback();
		}
		if (tx != null && tx.getStatus() == TransactionStatus.ACTIVE) {
			if (!noCommit)
				tx.commit();
		}
	}

	public static void rollbackTransaction() {
		if (Factory.getSession().getTransaction() != null && Factory.getSession().getTransaction().getStatus() == TransactionStatus.ACTIVE)
			Factory.getSession().getTransaction().rollback();
	}

	public static void rollbackTransaction(Transaction tx) {
		if (tx != null && tx.getStatus() == TransactionStatus.ACTIVE)
			tx.rollback();
	}

	/**
	 * Gets an object of type T from Hibernate.
	 * 
	 * @param <T>
	 * @param classEntity
	 * @param id
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T get(Class<T> classEntity, long id) {
		Object object = Factory.getSession().get(classEntity, id);

		if (object == null)
			return null;

		return (T) object;
	}

	/**
	 * Saves the specified object to the database.
	 * 
	 * @param <T>
	 * @param object
	 */
	public static void save(DataObject object) {
		if (object == null)
			throw new NullPointerException("Object supplied is null");

		if (object.getId() != null && object.getId() > 0)
			Factory.getSession().saveOrUpdate(object);
		else {
			Factory.getSession().save(object);
		}
	}

	public static void saveUsers(User users) {
		if (users == null)
			throw new NullPointerException("Object supplied is null");
		Factory.getSession().save(users);
	}

	public static void updateUsers(User users) {
		if (users == null)
			throw new NullPointerException("Object supplied is null");
		Factory.getSession().update(users);
	}

	/**
	 * Deletes the specified object.
	 * 
	 * @param <T>
	 * @param object
	 */
	public static void delete(DataObject object) {
		try {
			Factory.beginTransaction();
			Factory.getSession().delete(object);
			Factory.commitTransaction();

		} catch (Exception ex) {
			logger.error("DB Record Delete Error:", ex);
			Factory.rollbackTransaction();
		}

	}

	/**
	 * Creates an hibernate criteria object which is used to query against objects.
	 * 
	 * @param classArg
	 * @return
	 */
	public static <T> Criteria createCriteria(Class<T> classArg) {

		Criteria criteria = Factory.getSession().createCriteria(classArg);
		// Factory.commitTransaction();

		return criteria;
	}

	/**
	 * Creates an hibernate criteria object with the specified alias.
	 * 
	 * @param classArg
	 * @param alias
	 * @return
	 */
	public static <T> Criteria createCriteria(Class<T> classArg, String alias) {
		return Factory.getSession().createCriteria(classArg, alias);
	}

	/**
	 * Serializes an object into a byte array.
	 * 
	 * @param object
	 * @return
	 */
	public static byte[] serialize(Object object) throws IOException {
		ByteArrayOutputStream stream = new ByteArrayOutputStream();

		try {
			ObjectOutputStream serializer = new ObjectOutputStream(stream);
			serializer.writeObject(object);
			serializer.close();
		} catch (IOException ex) {
			Factory.handleException(ex);
		}

		byte[] result = stream.toByteArray();
		stream.close();
		return result;
	}

	private static void handleException(IOException ex) {
		// TODO Auto-generated method stub

	}

	/**
	 * Merges the specified instance.
	 * 
	 * @param obj
	 */
	public static void merge(DataObject obj) {
		Factory.getSession().merge(obj);
	}

	/**
	 * Refreshes the specified instance.
	 * 
	 * @param approval
	 */
	public static void refresh(DataObject obj) {
		Factory.getSession().refresh(obj);
	}

	/**
	 * Saves or updates the object.
	 * 
	 * @param obj
	 */
	public static void saveOrUpdate(DataObject obj) {
		Factory.getSession().saveOrUpdate(obj);
	}

	/**
	 * Evicts the specified object from the session.
	 * 
	 * @param obj
	 */
	public static void evict(DataObject obj) {
		Factory.getSession().evict(obj);
	}

	/**
	 * Searches all available search sources (this is used for the back page only).
	 * 
	 * @param search
	 * @return
	 */

	// public static PagedResults<Object> search(InternalSearchCriteria
	// criteria, User user) {
	// List<Class<?>> types = new ArrayList<Class<?>>();
	// List<Object> result = new ArrayList<Object>();
	// FullTextSession fullTextSession = Factory.getFullTextSession();
	// List<InternalSearch> searches = InternalSearchFactory.getForUser(user);
	// int totalRows = 0;
	// BooleanQuery query = new BooleanQuery();
	//
	// // loop through each type
	// for (InternalSearch search : searches) {
	// try {
	// // add the type
	// BooleanQuery typeQuery = new BooleanQuery();
	// typeQuery.add(new TermQuery(new Term(DocumentBuilder.CLASS_FIELDNAME,
	// search.getResultType())), Occur.MUST);
	//
	// // add the fields
	// String [] searchFields = search.getSearchFields().split(",");
	// BooleanQuery subQuery = new BooleanQuery();
	// for (String field : searchFields) {
	// subQuery.add(new TermQuery(new Term(field, "(" +
	// criteria.getText().toLowerCase() + ")")), Occur.SHOULD);
	// }
	//
	// // apply the analyzer to the sub query
	// try {
	// String queryString = subQuery.toString();
	// // typeQuery.add(new QueryParser(queryString, new
	// StandardAnalyzer()).parse(queryString), Occur.MUST);
	// typeQuery.add(new QueryParser(queryString, new
	// WhitespaceAnalyzer()).parse(queryString), Occur.MUST);
	// } catch (ParseException ex) {
	// Factory.log.error("Could not parse query.", ex);
	// }
	//
	// query.add(typeQuery, Occur.SHOULD);
	//
	// types.add(Class.forName(search.getResultType()));
	// } catch (ClassNotFoundException ex) {
	// Factory.log.error("Internal search class not found: " +
	// search.getResultType() + " for search: " + search.getName(), ex);
	// }
	// }
	//
	// // run the query
	// FullTextQuery searchQuery = fullTextSession.createFullTextQuery(query,
	// types.toArray(new Class[0]));
	// searchQuery.setFirstResult(criteria.getPageNumber() *
	// criteria.getPageSize());
	// searchQuery.setMaxResults(criteria.getPageSize());
	// searchQuery.enableFullTextFilter("users").setParameter("user", user);
	// searchQuery.enableFullTextFilter("documents").setParameter("user", user);
	// searchQuery.enableFullTextFilter("accounts").setParameter("user", user);
	// searchQuery.enableFullTextFilter("content").setParameter("user", user);
	// Factory.log.debug("Search Query: " + query.toString());
	//
	// result = searchQuery.list();
	// totalRows = searchQuery.getResultSize();
	//
	// return new PagedResults<Object>(result, totalRows);
	// }
	//
	// /**
	// * Searches a specific type.
	// * @param criteria
	// * @param type
	// * @param user
	// * @return
	// */
	// @SuppressWarnings("unchecked")
	// public static PagedResults<Object> search(InternalSearchCriteria
	// criteria, Class<?> type, User user) {
	// FullTextSession fullTextSession = Factory.getFullTextSession();
	// // get the internal search by the specified type
	// InternalSearch search = InternalSearchFactory.getByType(type, user);
	// List<Object> result = null;
	// String [] queries = null;
	// String [] fields = null;
	// int totalRows = 0;
	//
	// if (search == null)
	// return new PagedResults<Object>(new ArrayList<Object>(), 0);
	//
	// // get the fields
	// fields = search.getSearchFields().split(",");
	// queries = new String[fields.length];
	//
	// // build the array of term queries
	// for (int index = 0; index < fields.length; index++) {
	// // add the query
	// queries[index] = criteria.getText();
	// }
	//
	// // execute the search
	// try {
	// org.apache.lucene.search.Query query =
	// MultiFieldQueryParser.parse(queries, fields, new StandardAnalyzer());
	// FullTextQuery searchQuery = fullTextSession.createFullTextQuery(query,
	// type);
	// searchQuery.setFirstResult(criteria.getPageNumber() *
	// criteria.getPageSize());
	// searchQuery.setMaxResults(criteria.getPageSize());
	// searchQuery.enableFullTextFilter("users").setParameter("user", user);
	// searchQuery.enableFullTextFilter("documents").setParameter("user", user);
	// searchQuery.enableFullTextFilter("accounts").setParameter("user", user);
	// searchQuery.enableFullTextFilter("content").setParameter("user", user);
	// Factory.log.debug("Search Query: " + query.toString());
	//
	// result = searchQuery.list();
	// totalRows = searchQuery.getResultSize();
	// } catch (ParseException e) {
	// Factory.log.error("Could not parse search query", e);
	// }
	//
	// return new PagedResults<Object>(result, totalRows);
	// }
	/**
	 * Creates a hibernate query.
	 * 
	 * @param query
	 * @return
	 */
	public static Query createQuery(String query) {
		return Factory.getSession().createQuery(query);
	}

	public static FullTextSession getFullTextSession() {
		return Search.getFullTextSession(Factory.getSession());
	}

	/**
	 * Closes the session factory. Should only be called when an application is closing.
	 */
	public static void close() {
		if (!isNoClose())
			Factory.getFactory().close();
	}

	/**
	 * @param noCommit
	 *            the noCommit to set
	 */
	public static void setNoCommit(boolean noCommit) {
		Factory.noCommit = noCommit;
	}

	/**
	 * @return the noCommit
	 */
	public static boolean isNoCommit() {
		return noCommit;
	}

	/**
	 * @param autoRollback
	 *            the autoRollback to set
	 */
	public static void setAutoRollback(boolean autoRollback) {
		Factory.autoRollback = autoRollback;
	}

	/**
	 * @return the autoRollback
	 */
	public static boolean isAutoRollback() {
		return autoRollback;
	}

	/**
	 * @param noClose
	 *            the noClose to set
	 */
	public static void setNoClose(boolean noClose) {
		Factory.noClose = noClose;
	}

	/**
	 * @return the noClose
	 */
	public static boolean isNoClose() {
		return noClose;
	}
}
